package top.zywork.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import top.zywork.common.AuthUtils;
import top.zywork.common.DateUtils;
import top.zywork.common.ReflectUtils;
import top.zywork.common.WebUtils;
import top.zywork.dos.AppAuthConfig;
import top.zywork.dto.AppAuthDTO;
import top.zywork.enums.AppAuthEnableEnum;
import top.zywork.enums.ContentTypeEnum;
import top.zywork.enums.SysConfigEnum;
import top.zywork.service.AppAuthService;
import top.zywork.service.SysConfigService;
import top.zywork.vo.ResponseStatusVO;
import top.zywork.wrapper.AppAuthHttpServletRequestWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * AppAuthFilter过滤器类<br/>
 * <p>
 * 创建于2020-01-03<br/>
 *
 * @author 王振宇
 * @version 1.0
 */
@Component(value = "myAppAuthFilter")
@WebFilter(filterName = "appAuthFilter", urlPatterns = "/*")
@Slf4j
public class AppAuthFilter implements Filter {

    private static final int TIME_UNITS_SECONDS = 60;
    private static final int TIME_UNITS_MILLIS = 1000;

    private List<String> ignoreUrlList = new ArrayList<>();

    private List<String> ignoreHeaderReferList = new ArrayList<>();

    private AppAuthService appAuthService;

    private SysConfigService sysConfigService;

    @Override
    public void init(FilterConfig config) {
        log.info("AppAuthFilter过滤器启动......");
        getAppAuthFilterClass();
    }

    private void getAppAuthFilterClass() {
        log.info("开始获取不需要授权判断的URL......");
        List<Class<?>> classList = ReflectUtils.getClasses("top.zywork.controller", false, top.zywork.annotation.AppAuthFilter.class);
        if (classList != null && classList.size() > 0) {
            for (Class<?> clazz : classList) {
                top.zywork.annotation.AppAuthFilter appAuthFilter = clazz.getDeclaredAnnotation(top.zywork.annotation.AppAuthFilter.class);
                String[] ignoreUrls = appAuthFilter.ignoreUrls();
                if (ignoreUrls.length > 0) {
                    ignoreUrlList.addAll(Arrays.asList(ignoreUrls));
                }
                String[] ignoreHeaderRefers = appAuthFilter.ignoreHeaderRefers();
                if (ignoreHeaderRefers.length > 0) {
                    ignoreHeaderReferList.addAll(Arrays.asList(ignoreHeaderRefers));
                }
            }
        }
        log.info("已获取不需要授权判断的URL......");
    }

    /**
     * 0、检查是否是认证用户，如果未认证，则直接放行进入到/auth/require请求
     * 1、检查发起请求的appid是否存在，不存在则不合法
     * 2、检查appid是否正确，不正确则不合法
     * 3、检查appid对应的app是否开启了授权规则，如果未开启，则直接放行
     * 4、检查是否配置授权规则，如果未配置，则直接放行
     * 5、检查授权规则中是否开启授权，如果未开启，则直接放行
     * 6、检查授权规则中是否配置过滤url，如果未配置，则直接放行
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        long currentTimestamp = DateUtils.currentTimeMillis();
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        if (isIgnoreUrl(httpServletRequest)) {
            // 如果是不需要授权的url
            chain.doFilter(request, response);
            return;
        }
        if (isIgnoreHeaderRefer(httpServletRequest)) {
            // 如果是需要忽略的header referer
            chain.doFilter(request, response);
            return;
        }
        String appid = httpServletRequest.getHeader(AuthUtils.HEADER_PREFIX + AuthUtils.PARAM_APPID);
        String reqUrl = httpServletRequest.getHeader(AuthUtils.HEADER_PREFIX + AuthUtils.PARAM_REQURL);
        String timestampStr = httpServletRequest.getHeader(AuthUtils.HEADER_PREFIX + AuthUtils.PARAM_TIMESTAMP);
        String nonceStr = httpServletRequest.getHeader(AuthUtils.HEADER_PREFIX + AuthUtils.PARAM_NONCESTR);
        String sign = httpServletRequest.getHeader(AuthUtils.HEADER_PREFIX + AuthUtils.PARAM_SIGN);
        if (StringUtils.isEmpty(appid)) {
            WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，缺少appid", null));
            return;
        }
        AppAuthDTO appAuthDTO = appAuthService.getAppAuthByAppid(appid);
        if (appAuthDTO == null) {
            WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，appid错误", null));
            return;
        }
        if (appAuthDTO.getEnableAuth() == AppAuthEnableEnum.DISABLE.getValue().byteValue()) {
            // 此appid对应的app不需要启用app授权，请求直接放行
            chain.doFilter(request, response);
            return;
        }
        AppAuthConfig appAuthConfig = sysConfigService.getByName(SysConfigEnum.APP_AUTH_CONFIG.getValue(), AppAuthConfig.class);
        if (appAuthConfig == null) {
            // 无appAuth配置，直接放行
            chain.doFilter(request, response);
            return;
        }
        if (!appAuthConfig.getEnableAppAuth()) {
            // 没有启用app授权，直接放行
            chain.doFilter(request, response);
            return;
        }
        if (StringUtils.isEmpty(reqUrl) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestampStr) || StringUtils.isEmpty(nonceStr)) {
            WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，缺少reqUrl, timestamp, nonceStr或sign", null));
            return;
        }
        try {
            if (currentTimestamp - Long.parseLong(timestampStr) > appAuthConfig.getValidTimeInMinutes() * TIME_UNITS_SECONDS * TIME_UNITS_MILLIS) {
                WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，超出请求有效时间", null));
                return;
            }
        } catch (NumberFormatException e) {
            WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，timestamp错误", null));
            return;
        }
        String contentType = httpServletRequest.getContentType();
        if (StringUtils.isEmpty(contentType)) {
            // get请求没有content-type
            if (!AuthUtils.isSignatureValid(getParamsData(appid, reqUrl, timestampStr, nonceStr, sign, httpServletRequest.getParameterMap()), appAuthDTO.getAppSecret())) {
                WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，签名错误", null));
                return;
            }
            chain.doFilter(request, response);
        } else if (ContentTypeEnum.JSON.getValue().toLowerCase().contains(contentType.toLowerCase())) {
            // json请求
            AppAuthHttpServletRequestWrapper appAuthHttpServletRequestWrapper = new AppAuthHttpServletRequestWrapper(httpServletRequest);
            Map<String, String> data = getHeaderData(appid, reqUrl, timestampStr, nonceStr, sign);
            data.put(AuthUtils.PARAM_POST_JSON_STR, appAuthHttpServletRequestWrapper.getPostJsonStr());
            if (!AuthUtils.isSignatureValid(data, appAuthDTO.getAppSecret())) {
                WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，签名错误", null));
                return;
            }
            chain.doFilter(appAuthHttpServletRequestWrapper, response);
        } else if (ContentTypeEnum.FORM_URLENCODED.getValue().toLowerCase().contains(contentType.toLowerCase())) {
            // x-www-form-urlencoded请求
            if (!AuthUtils.isSignatureValid(getParamsData(appid, reqUrl, timestampStr, nonceStr, sign, httpServletRequest.getParameterMap()), appAuthDTO.getAppSecret())) {
                WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，签名错误", null));
                return;
            }
            chain.doFilter(request, response);
        } else if (ContentTypeEnum.FORM_DATA.getValue().toLowerCase().contains(contentType.toLowerCase())) {
            // multipart/form-data文件上传请求
            if (!AuthUtils.isSignatureValid(getHeaderData(appid, reqUrl, timestampStr, nonceStr, sign), appAuthDTO.getAppSecret())) {
                WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，签名错误", null));
                return;
            }
            chain.doFilter(request, response);
        } else {
            WebUtils.outResponseStatusVO(httpServletResponse, ResponseStatusVO.requestInvalid("请求不合法，不支持的请求数据类型", null));
        }
    }

    private boolean isIgnoreUrl(HttpServletRequest request) {
        for (String ignoreUrl : ignoreUrlList) {
            AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignoreUrl);
            if (matcher.matches(request)) {
                return true;
            }
        }
        return false;
    }

    private boolean isIgnoreHeaderRefer(HttpServletRequest request) {
        String headerRefer = request.getHeader("Referer");
        if (StringUtils.isEmpty(headerRefer)) {
            return false;
        }
        for (String ignoreHeaderRefer : ignoreHeaderReferList) {
            if (headerRefer.contains(ignoreHeaderRefer)) {
                return true;
            }
        }
        return false;
    }

    private Map<String, String> getHeaderData(String appid, String reqUrl, String timestamp, String nonceStr, String sign) {
        Map<String, String> data = new HashMap<>(5);
        data.put(AuthUtils.PARAM_APPID, appid);
        data.put(AuthUtils.PARAM_REQURL, reqUrl);
        data.put(AuthUtils.PARAM_TIMESTAMP, timestamp);
        data.put(AuthUtils.PARAM_NONCESTR, nonceStr);
        data.put(AuthUtils.PARAM_SIGN, sign);
        return data;
    }

    /**
     * 去获取get请求或post(x-www-form-urlencoded)请求需要用于加密的数据
     * @param appid
     * @param reqUrl
     * @param timestamp
     * @param nonceStr
     * @param sign
     * @param parameterMap
     * @return
     */
    private Map<String, String> getParamsData(String appid, String reqUrl, String timestamp, String nonceStr, String sign, Map<String, String[]> parameterMap) {
        Map<String, String> data = getHeaderData(appid, reqUrl, timestamp, nonceStr, sign);
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] values = entry.getValue();
            for (String value : values) {
                if (StringUtils.isNotEmpty(value.trim())) {
                    if (data.containsKey(key)) {
                        data.put(key, data.get(key) + "," + value);
                    } else {
                        data.put(key, value);
                    }
                }
            }
        }
        return data;
    }

    @Override
    public void destroy() {
        log.info("AppAuthFilter过滤器销毁......");
    }

    @Autowired
    public void setAppAuthService(AppAuthService appAuthService) {
        this.appAuthService = appAuthService;
    }

    @Autowired
    public void setSysConfigService(SysConfigService sysConfigService) {
        this.sysConfigService = sysConfigService;
    }
}